package com.tqz.rc.client.naming;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tqz.rc.api.BeatInfo;
import com.tqz.rc.api.Instance;
import com.tqz.rc.client.beat.BeatReactor;
import com.tqz.rc.client.naming.cache.ServiceInfoHolder;
import com.tqz.rc.client.push.PushReceiver;
import com.tqz.rc.common.constant.FieldConstant;
import com.tqz.rc.common.constant.HttpMethodConstant;
import com.tqz.rc.common.constant.UrlConstant;
import com.tqz.rc.common.exception.RcException;
import com.tqz.rc.common.result.Result;
import com.tqz.rc.common.util.NamingUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.*;

/**
 * <p>
 *
 * @author tianqingzhao
 * @since 2021/8/30 10:49
 */
public class NamingHttpClientProxy implements NamingClientProxy {

    private RestTemplate restTemplate = new RestTemplate();

    private BeatReactor beatReactor;

    private PushReceiver pushReceiver;

    public NamingHttpClientProxy(ServiceInfoHolder serviceInfoHolder, Properties properties) {
        this.beatReactor = new BeatReactor(this);
        this.pushReceiver = new PushReceiver(serviceInfoHolder, properties);
    }

    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws RcException {
        // 对于临时实例的节点来说，在服务注册后，Nacos Client会维护一个定时心跳来持续通知Nacos Server，
        // 说明服务一直处于可用状态，防止被剔除。默认5s发送一次心跳。
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = beatReactor.buildBeatInfo(serviceName, instance);
            beatReactor.addBeatInfo(beatInfo);
        }
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);

        // 通过http方式向服务端发送服务注册
        Map<String, String> params = buildRegisterInstanceParams(serviceName, groupedServiceName, instance);
        reqApi(UrlConstant.INSTANCE_REGISTER_URL, params, HttpMethodConstant.POST_METHOD);
    }

    @Override
    public List<Instance> selectInstances(String namespaceId, String serviceName, String groupName) {
        Map<String, String> params = buildSelectInstancesParams(namespaceId, serviceName, groupName);
        Result<List<LinkedHashMap>> result = reqApi(UrlConstant.SELECT_INSTANCES_URL, params, HttpMethodConstant.GET_METHOD);
        return convertLinkedHashMap2ArrayList(result.getData());
    }

    /**
     * 构建查询实例的参数。
     *
     * @param namespaceId 命名空间的id
     * @param serviceName 服务名称
     * @param groupName   分组名称
     * @return
     */
    private Map<String, String> buildSelectInstancesParams(String namespaceId, String serviceName, String groupName) {
        Map<String, String> params = new HashMap<>();
        params.put(FieldConstant.NAMESPACE_ID_PARAM, namespaceId);
        params.put(FieldConstant.SERVICE_NAME_PARAM, serviceName);
        params.put(FieldConstant.GROUP_NAME_PARAM, groupName);
        return params;
    }

    /**
     * 构建注册实例的参数。
     *
     * @param serviceName 服务名称
     * @param groupName   分组名称
     * @param instance    实例信息
     * @return
     */
    private Map<String, String> buildRegisterInstanceParams(String serviceName, String groupName, Instance instance) {
        Map<String, String> params = new HashMap<>();

        params.put(FieldConstant.NAMESPACE_ID_PARAM, instance.getNamespaceId());
        params.put(FieldConstant.SERVICE_NAME_PARAM, serviceName);
        params.put(FieldConstant.GROUP_NAME_PARAM, groupName);
        params.put(FieldConstant.IP_PARAM, instance.getIp());
        params.put(FieldConstant.PORT_PARAM, String.valueOf(instance.getPort()));
        params.put(FieldConstant.CLUSTER_NAME_PARAM, instance.getClusterName());
        params.put(FieldConstant.HEALTHY_PARAM, String.valueOf(instance.isHealthy()));

        return params;
    }

    /**
     * 请求对应的api。
     *
     * @param url    请求的url
     * @param params 参数
     * @param method 请求方式，参见：{@link HttpMethodConstant}
     * @return
     */
    public Result reqApi(String url, Map<String, String> params, String method) {
        // TODO 集群模式下调用一台服务地址去请求即可，然后一台服务去同步其他服务的数据。目前写死一个机器节点的地址 `http://localhost:8080`
        return callServer(url, params, method, "http://localhost:8080/");
    }

    /**
     * 调用服务端。 TODO 该方法可改为工厂加策略模式。或者自定义参考nacos使用原生jdk的网络自定义封装一套http请求。
     *
     * @param url           请求的url
     * @param params        参数
     * @param method        请求方式
     * @param currentServer 请求的服务器地址
     * @return
     */
    public Result callServer(String url, Map<String, String> params, String method, String currentServer) {
        String requestUrl = currentServer + url;

        Result result = null;
        // post请求
        if (HttpMethodConstant.POST_METHOD.equals(method)) {
            ResponseEntity<Result> httpResponseResponseEntity = restTemplate.postForEntity(requestUrl, getHttpEntity(params), Result.class);
            result = httpResponseResponseEntity.getBody();
        }
        // get请求
        else if (HttpMethodConstant.GET_METHOD.equals(method)) {
            result = restTemplate.getForObject(buildGetParamsUrl(requestUrl, params), Result.class);
        }
        // put请求
        else if (HttpMethodConstant.PUT_METHOD.equals(method)) {
            restTemplate.put(requestUrl, getHttpEntity(params), Result.class);
            result = new Result();
        }

        return result;
    }

    /**
     * 构建get请求的url的参数，也就是把参数拼接到url上面。
     *
     * @param url    原来的url
     * @param params 参数
     * @return
     */
    private String buildGetParamsUrl(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder(url + "?");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    /**
     * 根据请求参数获取 {@link HttpEntity}
     *
     * @param params 请求的参数
     * @return
     */
    private HttpEntity getHttpEntity(Map<String, String> params) {
        MultiValueMap<String, String> multiValueMap = convertHashMap2LinkedMultiValueMap(params);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity httpEntity = new HttpEntity(multiValueMap, headers);

        return httpEntity;
    }

    /**
     * 把请求的 `HashMap` 参数转为 `LinkedMultiValueMap`
     *
     * @param paramsMap 请求的参数，为{@link HashMap} 类型
     * @return
     */
    private MultiValueMap<String, String> convertHashMap2LinkedMultiValueMap(Map<String, String> paramsMap) {
        MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>();

        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
            result.add(entry.getKey(), entry.getValue());
        }

        return result;
    }

    /**
     * 把 `LinkedHashMap` 转换为 `ArrayList`
     *
     * @param data 被转换的数据
     * @return
     */
    private List<Instance> convertLinkedHashMap2ArrayList(List<LinkedHashMap> data) {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.convertValue(data, new TypeReference<List<Instance>>() {
        });
    }

    @Override
    public void shutdown() {
        beatReactor.shutdown();
    }
}
